from time import sleep_us, time
from machine import Pin
from micropython import const

# HX711的异常类
class HX711Exception(Exception):
    pass

# 无效模式异常类
class InvalidMode(HX711Exception):
    pass

# 设备尚未就绪异常类
class DeviceIsNotReady(HX711Exception):
    pass

# HX711类
class HX711(object):
    """
    Avia Semiconductor的HX711 24位模数转换器的MicroPython驱动程序
    """
    # 通道A的增益选择
    CHANNEL_A_128 = const(1)
    CHANNEL_A_64 = const(3)
    CHANNEL_B_32 = const(2)

    # 数据位数
    DATA_BITS = const(24)
    MAX_VALUE = const(0x7fffff)
    MIN_VALUE = const(0x800000)
    READY_TIMEOUT_SEC = const(5)  # 就绪超时时间（秒）
    SLEEP_DELAY_USEC = const(80)  # 延迟时间（微秒）

    # 初始化方法
    def __init__(self, d_out: int, pd_sck: int, channel: int = CHANNEL_A_128):
        self.d_out_pin = Pin(d_out, Pin.IN)
        self.pd_sck_pin = Pin(pd_sck, Pin.OUT, value=0)
        self.channel = channel

    # 用于打印HX711对象信息
    def __repr__(self):
        return "HX711在通道 %s上，增益=%s" % self.channel

    # 从二进制补码转换为整数
    def _convert_from_twos_complement(self, value: int) -> int:
        """
        将给定的整数从二进制补码格式转换过来
        """
        if value & (1 << (self.DATA_BITS - 1)):
            value -= 1 << self.DATA_BITS
        return value

    # 设置通道
    def _set_channel(self):
        """
        输入和增益选择由输入PD_SCK脉冲的数量控制
        3脉冲表示带增益64的A通道
        2脉冲表示带增益32的B通道
        1脉冲表示带增益128的A通道
        """
        for i in range(self._channel):
            self.pd_sck_pin.value(1)
            self.pd_sck_pin.value(0)

    # 等待HX711就绪
    def _wait(self):
        """
        如果在READY_TIMEOUT_SEC时间内HX711没有就绪
        将引发DeviceIsNotReady异常
        """
        t0 = time()
        while not self.is_ready():
            if time() - t0 > self.READY_TIMEOUT_SEC:
                raise DeviceIsNotReady()

    # 通道属性的获取方法
    @property
    def channel(self) -> tuple:
        """
        以元组(Channel, Gain)的形式获取当前输入通道
        """
        if self._channel == self.CHANNEL_A_128:
            return 'A', 128
        if self._channel == self.CHANNEL_A_64:
            return 'A', 64
        if self._channel == self.CHANNEL_B_32:
            return 'B', 32

    # 通道属性的设置方法
    @channel.setter
    def channel(self, value):
        """
        设置输入通道
        HX711.CHANNEL_A_128 - 带增益128的A通道
        HX711.CHANNEL_A_64 - 带增益64的A通道
        HX711.CHANNEL_B_32 - 带增益32的B通道
        """
        if value not in (self.CHANNEL_A_128, self.CHANNEL_A_64, self.CHANNEL_B_32):
            raise InvalidMode('增益应为 HX711.CHANNEL_A_128, HX711.CHANNEL_A_64, HX711.CHANNEL_B_32 中的一个')
        else:
            self._channel = value

        if not self.is_ready():
            self._wait()

        for i in range(self.DATA_BITS):
            self.pd_sck_pin.value(1)
            self.pd_sck_pin.value(0)

        self._set_channel()

    # 判断HX711是否就绪
    def is_ready(self) -> bool:
        """
        当输出数据尚未准备好检索时，数字输出引脚DOUT为高电平
        """
        return self.d_out_pin.value() == 0

    # 关闭HX711
    def power_off(self):
        """
        当PD_SCK从低电平变为高电平并保持高电平时间超过60微秒时，
        HX711进入掉电模式
        """
        self.pd_sck_pin.value(0)
        self.pd_sck_pin.value(1)
        sleep_us(self.SLEEP_DELAY_USEC)

    # 启动HX711
    def power_on(self):
        """
        当PD_SCK返回低电平时，HX711将复位并进入正常操作模式
        """
        self.pd_sck_pin.value(0)
        self.channel = self._channel

    # 读取HX711当前值
    def read(self, raw=False):
        """
        以当前增益和通道读取当前通道的值
        如果raw为True，HX711的输出将不会从二进制补码格式转换
        """
        if not self.is_ready():
            self._wait()

        raw_data = 0
        for i in range(self.DATA_BITS):
            self.pd_sck_pin.value(1)
            self.pd_sck_pin.value(0)
            raw_data = raw_data << 1 | self.d_out_pin.value()
        self._set_channel()

        if raw:
            return raw_data
        else:
            return self._convert_from_twos_complement(raw_data)
